﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace JerrodSoft.Serialization.Xml
{
    /// <summary>
    /// Rethink caching mechaism and breaking this class down into caching and helper methods.
    /// </summary>
    public static class ObjectCache
    {
        private static System.Collections.Hashtable Items { get; set; }
        public readonly static List<Type> PrimitiveTypes;
        static ObjectCache()
        {
            PrimitiveTypes = new List<Type>()
            {
                typeof(Int16),
                typeof(Int32),
                typeof(Int64),
                typeof(double),
                typeof(float),
                typeof(char),
                typeof(Guid),
                typeof(string),
                typeof(byte),
                typeof(UInt16),
                typeof(UInt32),
                typeof(UInt64),
                typeof(DateTime),
                typeof(bool),
                typeof(decimal),
                typeof(object)
            };

            Items = new System.Collections.Hashtable();
        }

        public static PropertyInfo GetTypeProperty(Type type, string propertyName)
        {
            TypeCacheItem item = null;
            if (Items.ContainsKey(type))
            {
                item = Items[type] as TypeCacheItem;
            }
            else
                item = CacheType(type);

            return item.AssociatedProperties[propertyName.ToLower()] as PropertyInfo;
        }

        public static TypeCacheItem CacheType(Type type)
        {
            if (Items.ContainsKey(type))
                return Items[type] as TypeCacheItem;

            TypeCacheItem item = new TypeCacheItem();
            Items.Add(type, item);
            item.IsCollection = IsCollectionType(type);
            item.IsPrimitive = IsPrimiviteType(type);
            item.Target = type;
            item.AssociatedProperties = new System.Collections.Hashtable();
            item.AssociatedAttributes = new System.Collections.Hashtable();

            foreach (var property in type.GetProperties())
                item.AssociatedProperties.Add(property.Name.ToLower(), property);
            foreach (var attribute in type.GetCustomAttributes(true))
                item.AssociatedAttributes.Add(attribute.GetType(), attribute);

            return item;
        }

        public static TypeCacheItem GetType(object obj)
        {
            if (obj == null)
                return null;
            Type type = obj.GetType();
            if (!Items.ContainsKey(type))
                CacheType(type);

            return Items[type] as TypeCacheItem;
        }

        public static void Add(TypeCacheItem item)
        {
            Items.Add(item.Target, item);
        }

        public static IEnumerable<PropertyInfo> GetProperties(Type type)
        {
            TypeCacheItem item = null;
            if (!Items.ContainsKey(type))
                item = CacheType(type);
            else
                item = Items[type] as TypeCacheItem;

            return item.AssociatedProperties.Values.Cast<PropertyInfo>();
            //return item.AssociatedProperties.Cast<PropertyInfo>();
        }

        public static bool IsPrimiviteType(Type type)
        {
            if (!Items.ContainsKey(type))
                CacheType(type);

            return type.IsPrimitive || PrimitiveTypes.Contains(type) || type.IsEnum;
        }

        public static bool IsCollectionType(Type type)
        {
            if (!Items.ContainsKey(type))
                CacheType(type);

            if (type.IsArray)
                return true;

            var enumType = type.GetInterface(typeof(System.Collections.IEnumerable).FullName);
            if (enumType != null)
            {
                var args = type.GetGenericArguments();
                if (args != null && args.Length > 1)
                    throw new Exception("Cannot represent an array of more than one type.");

                return true;
            }

            return false;
        }

        public static T GetAttribute<T>(Type type) where T : Attribute
        {
            if (!Items.ContainsKey(type))
                CacheType(type);

            Type targetType = typeof(T);
            var item = (Items[type] as TypeCacheItem);
            if (!item.AssociatedAttributes.ContainsKey(targetType))
                return null;
            else
                return (T)Convert.ChangeType(item.AssociatedAttributes[targetType], targetType);
        }

        public static Type GetGenericArgumentForCollection(Type type)
        {
            var arguments = type.GetGenericArguments();
            if (arguments.Length == 0 && type.BaseType != null)
            {
                return GetGenericArgumentForCollection(type.BaseType);
            }
            else if (arguments.Length > 1)
                throw new Exception("Serializer will only allow for one generic type for collections.");
            else if (arguments.Length == 1)
                return arguments[0];

            return null;
        }
    }
}
